﻿using CommonQuery.Builder;
using EntityFramework.Extensions;
using EntityframeworkHelper.Data.EntityExtend;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace EntityFrameworkHelper.Repositories
{
    /// <summary>
    /// Class GenericRepository.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TContext">The type of the t context.</typeparam>
    /// <typeparam name="TKey">The type of the t key.</typeparam>
    internal class Repository<T, TContext, TKey> : IRepository<T>
        where T : EntityBase<TKey> where TContext : DbContext, new()
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Repository{T, TContext, TKey}"/> class.
        /// </summary>
        /// <param name="_entities">The _entities.</param>
        public Repository(TContext _entities)
        {
            this.Entities = _entities;
        }

        /// <summary>
        ///     Gets the entities.
        /// </summary>
        /// <value>The entities.</value>
        private TContext Entities { get; }

        #region IRepository<T> Members

        /// <summary>
        ///     Adds the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public void Add(T entity)
        {
            this.Entities.Set<T>().Add(entity);
        }

        /// <summary>
        ///     Adds for batching.
        /// </summary>
        /// <param name="entities">The entities.</param>
        public void AddForBatchingNoContext(IEnumerable<T> entities)
        {
            this.Entities.Set<T>().AddRange(entities);
        }

        /// <summary>
        ///     Attaches the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public void Attach(T entity)
        {
            this.Entities.Set<T>().Attach(entity);
            this.Entities.Entry(entity).State = EntityState.Modified;
        }

        /// <summary>
        ///     Attaches for batching asynchronous.
        ///     (注意：生成SQL并启用底层事务机制，但和原始的EF中的Context无关，所以不要在DbContext上下文使用)
        /// </summary>
        /// <param name="predicate">The predicate.</param>
        /// <param name="updateExpression">The update expression.</param>
        /// <returns>Task&lt;System.Boolean&gt;.</returns>
        public async Task<bool> AttachForBatchingNoContextAsync(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> updateExpression)
        {
            return await this.Entities.Set<T>().Where(predicate).UpdateAsync(updateExpression) > 0;
        }

        /// <summary>
        ///     Attaches for batching asynchronous.
        ///     (注意：生成SQL并启用底层事务机制，但和原始的EF中的Context无关，所以不要在DbContext上下文使用)
        /// </summary>
        /// <param name="qb">The qb.</param>
        /// <param name="updateExpression">The update expression.</param>
        /// <returns>Task&lt;System.Boolean&gt;.</returns>
        public async Task<bool> AttachForBatchingNoContextAsync(BaseQueryBuilder qb, Expression<Func<T, T>> updateExpression)
        {
            return await this.Entities.Set<T>().Where(qb).UpdateAsync(updateExpression) > 0;
        }

        /// <summary>
        ///     Attaches for batching with context asynchronous.
        ///     批量更新可用于DbContext上下文
        /// </summary>
        /// <param name="entities">The entities.</param>
        /// <returns>Task&lt;System.Boolean&gt;.</returns>
        public void AttachForBatchingWithContextAsync(IList<T> entities)
        {
            foreach (T entity in entities)
            {
                DbSet<T> dbSet = this.Entities.Set<T>();
                try
                {
                    DbEntityEntry<T> entry = this.Entities.Entry(entity);
                    if (entry.State == EntityState.Detached)
                    {
                        dbSet.Attach(entity);
                        entry.State = EntityState.Modified;
                    }
                }
                catch (InvalidOperationException)
                {
                    T oldEntity = dbSet.Find(entity.Id);
                    this.Entities.Entry(oldEntity).CurrentValues.SetValues(entity);
                }
            }
        }

        /// <summary>
        ///     Deletes the specified entity.
        /// </summary>
        /// <param name="entity">The entity.</param>
        public void Delete(T entity)
        {
            this.Entities.Set<T>().Remove(entity);
        }

        /// <summary>
        ///     Gets the specified predicate.
        /// </summary>
        /// <param name="predicate">The predicate.</param>
        /// <returns>T.</returns>
        public Task<T> GetAsync(Func<T, bool> predicate)
        {
            return this.Entities.Set<T>().FindAsync(predicate);
        }

        /// <summary>
        ///     Gets the specified predicate.
        /// </summary>
        /// <param name="queryBuilder">The query builder.</param>
        /// <returns>T.</returns>
        public Task<T> GetAsync(BaseQueryBuilder queryBuilder)
        {
            return this.Entities.Set<T>().FindAsync(queryBuilder);
        }

        /// <summary>
        ///     Gets the asynchronous.
        /// </summary>
        /// <typeparam name="KeyType">The type of the key type.</typeparam>
        /// <param name="id">The identifier.</param>
        /// <returns>Task&lt;T&gt;.</returns>
        public Task<T> GetAsync<KeyType>(KeyType id)
        {
            return this.Entities.Set<T>().FindAsync(id);
        }

        /// <summary>
        ///     Gets the list asynchronous.
        /// </summary>
        /// <param name="queryBuilder">The query builder.</param>
        /// <returns>Task&lt;IList&lt;T&gt;&gt;.</returns>
        public async Task<IList<T>> GetListAsync(BaseQueryBuilder queryBuilder = null)
        {
            return queryBuilder != null ? await this.Entities.Set<T>().Where(queryBuilder).ToListAsync() : await this.Entities.Set<T>().ToListAsync();
        }

        /// <summary>
        ///     Gets all.
        /// </summary>
        /// <param name="predicate">The predicate.</param>
        /// <returns>IEnumerable&lt;T&gt;.</returns>
        public Task<IEnumerable<T>> QueryAsync(Func<T, bool> predicate = null)
        {
            return Task.FromResult(predicate != null ? this.Entities.Set<T>().Where(predicate) : this.Entities.Set<T>().AsEnumerable());
        }

        /// <summary>
        ///     query as an asynchronous operation.
        /// </summary>
        /// <param name="predicate">The predicate.</param>
        /// <returns>IEnumerable&lt;T&gt;.</returns>
        public async Task<IList<T>> QueryAsync(Expression<Func<T, bool>> predicate = null)
        {
            return predicate != null ? await this.Entities.Set<T>().Where(predicate).ToListAsync() : await this.Entities.Set<T>().ToListAsync();
        }

        #endregion IRepository<T> Members
    }
}